# -*- coding: utf-8 -*-

import sys

from PyQt5.QtCore import QCoreApplication, Qt, QDate, QTimer
from PyQt5.QtGui import QFont, QColor, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QToolTip, QPushButton, QMessageBox, QMainWindow, QAction, QLabel, \
    QHBoxLayout, QVBoxLayout, QGridLayout, QLineEdit, QLCDNumber, QSlider, QInputDialog, QColorDialog, QFontDialog, \
    QFrame, QFileDialog, QCheckBox, QProgressBar, QCalendarWidget, QComboBox


# Qwidget组件是PyQt5中所有用户界面类的基础类。
# 我们给QWidget提供了默认的构造方法。
# 默认构造方法没有父类。没有父类的widget组件将被作为窗口使用。
class Gui(QWidget):



    def init(self):


        # 设置窗体标签字体和大小（鼠标停留时显示）
        QToolTip.setFont(QFont('SansSerif', 20))
        # 设置窗体显示的富文本内容
        self.setToolTip('This is a <b>QWidget</b> widget')
        # 创建一个按钮组件
        #btn = QPushButton('关闭-有提示', self)
        # 设置按钮显示的富文本内容
        #btn.setToolTip('This is a <b>QPushButton</b> widget')
        # setHint()方法给了按钮一个推荐的大小
        #btn.resize(btn.sizeHint())
        # 设置按钮在窗体中的位置
        #btn.move(50, 50)
        # 简单的信号与槽机制
        # 按键实现关闭窗体本身self
        btn_close = QPushButton('关闭-无提示', self)
        btn_close.setGeometry(50, 80, 100,20)
        # 信号
        # PyQt5中，事件处理系统由信号&槽机制建立。如果我们点击了按钮，信号clicked被发送。
        # 槽可以是Qt内置的槽或Python 的一个方法调用。
        # QCoreApplication类包含了主事件循环；它处理和转发所有事件。
        # instance()方法给我们返回一个实例化对象。
        # 注意QCoreApplication类由QApplication创建。点击信号连接到quit()方法，将结束应用。
        # 事件通信在两个对象之间进行：发送者和接受者。发送者是按钮，接受者是应用对象。
        btn_close.clicked.connect(QCoreApplication.instance().quit)

        # 调整了widget组件的大小
        self.resize(400, 300)
        # 移动widget组件到一个位置
        self.move(500, 200)
        # 设置了我们窗口的标题。这个标题显示在标题栏中
        self.setWindowTitle('My First GUI for PyQt5')
        # 方法在屏幕上显示出widget。一个widget对象在这里第一次被在内存中创建，并且之后在屏幕上显示
        self.show()

    # 如果我们关闭一个QWidget，QCloseEvent类事件将被生成。要修改组件动作我们需要重新实现closeEvent()事件处理方法。
    def closeEvent(self, event):
        # 创建一个Messegebox
        reply = QMessageBox.question(self, "Message", "确定吗？",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()


# 菜单和工具栏
class menu_tool(QMainWindow):

    #
    def init(self):
        # 创建statusBar对象
        sb = self.statusBar()
        # 显示信息
        sb.showMessage('Ready')
        # 创建菜单栏menu对象
        menu = self.menuBar()
        # 增加子menu对象
        fileMenu = menu.addMenu('File')
        # 子对象增加事件
        # QAction是一个用于菜单栏、工具栏或自定义快捷键的抽象动作行为。
        # 当我们选中特定的动作，一个触发信号会被发射。信号连接到QApplication组件的quit()方法。这样就中断了应用。
        a = QAction("Exit", self)
        fileMenu.addAction(a.triggered.connect(QCoreApplication.instance().quit))






        self.setGeometry(300,300,250,150)
        self.setWindowTitle('StatusBar')
        self.show()

    # 重载了keyPressEvent()事件处理器
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            print("closed")
            self.close()



# 布局管理器
# 绝对定位不利于程序更新  移植
# 相对定位，即用到了布局管理器
class layout(QWidget):

    def init(self):
        # 创建标签对象
        lebal1 = QLabel('lebal 1', self)
        lebal2 = QLabel('lebal 2', self)
        lebal3 = QLabel('lebal 3', self)
        # 绝对定位
        #lebal1.move(10,10)
        #labal2.move(10, 30)

        # 相对定位
        # 布局管理器的布局管理类非常灵活
        # QHBoxLayout和QVBoxLayout是两个基础布局管理类，他们水平或垂直的线性排列组件
        # 创建垂直箱布局对象
        hbox = QHBoxLayout()
        # 创建水平箱布局
        vbox = QVBoxLayout()
        # 增加拉伸因子
        # 拉伸因子在两个按钮之前增加了一个可伸缩空间。这会将按钮推到窗口的右边
        hbox.addStretch(10)
        vbox.addStretch(10)
        # 垂直布局器上添加Widget对象
        hbox.addWidget(lebal2)
        hbox.addWidget(lebal1)
        # 将垂直布局器增加到水平布局器当中
        vbox.addLayout(hbox)

        # 创建风格布局器
        # 最常用的布局类是网格布局。这个布局使用行了列分割空间
        grid = QGridLayout()
        #
        names = ['Cls', 'Bck', '', 'Close',
                 '7', '8', '9', '/',
                 '4', '5', '6', '*',
                 '1', '2', '3', '-',
                 '0', '.', '=', '+']
        # 5行4列的坐标位置
        positions = [(i,j) for i in range(5) for j in range(4)]
        #  将坐标和名字对应
        for position, name in zip(positions, names):
            # names中有''空值，所以加上这一句
            if name == '':
                continue
            button = QPushButton(name)
            grid.addWidget(button,*position)

        # 创建行编辑对象
        titleEdit = QLineEdit()
        authorEdit = QLineEdit()
        reviewEdit = QLineEdit()
        # 加到7行1列位置
        grid.addWidget(lebal1,6,0)
        grid.addWidget(titleEdit,6,1)

        grid.addWidget(lebal2, 7, 0)
        grid.addWidget(authorEdit, 7, 1)

        grid.addWidget(lebal3, 8,0)
        # 参数说明
        # 前两个整数表示网格的行/列数，
        # 后两个整数表示对象占据的列/列数目
        grid.addWidget(reviewEdit,8,1,1,3)
        # 网络之间的间隙
        grid.setSpacing(10)





        # 将窗体本身设置为水平布局器
        self.setLayout(grid)
        self.setGeometry(300,300,100,100)
        self.show()


    pass


# 信号 & 槽
# PyQt5有一个独一无二的信号和槽机制来处理事件
# 信号和槽用于对象之间的通信。当指定事件发生，一个事件信号会被发射。
# 槽可以被任何Python脚本调用。当和槽连接的信号被发射时，槽会被调用。
class singal(QWidget):

    def init(self):

        # 创建数字显示lcd对象
        # QLCDNumber
        lcd = QLCDNumber(self)
        # 滑动条对象
        sld = QSlider(Qt.Horizontal, self)
        # 滑块条的valueChanged信号和lcd数字显示的display槽连接在一起

        sld.valueChanged.connect(lcd.display)

        vbox = QVBoxLayout()
        vbox.addWidget(lcd)
        vbox.addWidget(sld)

        self.setLayout(vbox)
        self.setGeometry(300,300,300,100)
        self.show()



        pass


# 对话框
class dialog(QWidget):

    def init(self):
        self.btn = QPushButton('Dialog', self)
        self.btn.move(20,20)
        # 将ckicked信号与自定义的槽连接
        # 当按键点击时，执行对应的方法
        self.btn.clicked.connect(self.file_dialog)

        self.le = QLineEdit(self)
        self.le.move(130,22)

        self.setGeometry(300,300,300,300)
        self.show()

    # 输入对话框
    def showDialog(self):
        # QInputDialog
        # 第一个字符串参数是对话框的标题
        # 第二个字符串参数是对话框内的消息文本
        # 对话框返回输入的文本内容和一个布尔值。如果我们点击了Ok按钮，布尔值就是true，反之布尔值是false
        text, ok = QInputDialog.getText(self, 'Input Dialog', 'Enter your name: ')
        if ok:
            self.le.setText(str(text))

    # 颜色对话框
    def color_dialog(self):
        # 定义一个颜色对象
        color = QColor(0,0,0)
        # 定义一个QFrame
        self.frame = QFrame(self)
        # 使用样式表来定义背景颜色
        self.frame.setStyleSheet("QWidget { background-color: %s }" % color.name())
        self.frame.setGeometry(100,100,100,100)
        self.frame.show()

        col = QColorDialog.getColor()
        # 这一行弹出颜色选择框。
        if col.isValid():
            self.frame.setStyleSheet("QWidget { background-color: %s }"
                                     % col.name())

        self.frame.show()

    # 字体对话框
    def font_dialog(self):

        font, ok = QFontDialog.getFont()
        if ok:
            self.le.setText(font.toString())

    # 文件对话框
    # 文件对话框是用于让用户选择文件或目录的对话框。可以选择文件的打开和保存。
    def file_dialog(self):
        # 第一个参数：新开的窗体的名称
        # 第二参数：默认打开的文件路径
        # 返回文件跟径
        file_name = QFileDialog.getOpenFileName(self, '打开文件', './')
        if file_name[0]:
            # 选中文件后，进行相应的操作即可
            pass


# 组件widgets: 组件（widgets）是构建一个应用的基础模块。
class widgets(QWidget):

    def init(self):

        # 定义一个Layout
        girdLayout = QGridLayout()

        # 复选框
        # 复选框组件有两种状态：选中和未选中。它是由一个选择框和一个标签组成的。
        # 一个应用中，复选框是典型的用来代表有效或无效状态的组件。
        #创建复选框，参数是它的名称
        checkBox = QCheckBox('Show title', self)
        girdLayout.addWidget(checkBox,1,1)
        # 我们需要设置窗口标题，所以我们必须选中复选框。
        # 如果不选中复选框，默认情况下复选框不会被选中所以窗口标题也不会被设置。
        checkBox.toggle()
        # 信号：复选框状态改变时，信号连接相应的槽
        checkBox.stateChanged.connect(self.changeTitle)

        # 进度条
        # 当我们处理耗时长的任务时，我们需要用到进度条组件。
        # 进度条组件提供了横向和纵向的进度条选择。程序员可以设置进度条的最大值和最小值。进度条的默认值是0~99
        pbar = QProgressBar(self)
        girdLayout.addWidget(pbar,1,2)
        # 进度值
        pbar.setValue(11)
        # 设置最大值
        pbar.setMaximum(100)

        # 日历组件
        # QCalendarWidget类提供了一个基于月的日历组件。
        # 它允许用户通过简单的直观的方式选择日期。
        cal = QCalendarWidget(self)
        #
        self.lb = QLabel('选择日期: ')
        girdLayout.addWidget(self.lb,3,1)
        cal.setGeometry(10,40,300,300)
        girdLayout.addWidget(cal, 2, 1,1,2)
        #信号：组件上选择了一个日期，信号连接相应的槽
        cal.clicked.connect(self.showDate)

        # 像素图,绘制图像
        # 像素图（QPixmap）是各种用于处理图像的组件中的一个。
        # 它是在屏幕上显示图片的最佳选择。我们将使用像素图来在窗口上显示一个图片。
        # 创建像素图对象
        pixmap = QPixmap('image.png')
        label_pixmap = QLabel(self)
        # 将像素图设置到标签上,通过标签来显示像素图
        label_pixmap.setPixmap(pixmap)
        # 标签加到布局上
        girdLayout.addWidget(label_pixmap,4,1,1,2)

        # 下拉列表框
        # 下拉列表框组件允许用户从列表中选择一个列表项。
        combox = QComboBox()
        for i in range(6):
            combox.addItem('选项: ' + str(i))
        girdLayout.addWidget(combox, 3,2)
        # 信号：下拉框被选中时，信号连接相应的槽
        combox.activated.connect(self.comboChange)





















        self.setLayout(girdLayout)
        self.show()

    # 自定义的槽方法
    def changeTitle(self, state):
        # 复选框组件的状态会传入changeTitle()方法的state参数。
        if state == Qt.Checked:
            self.setWindowTitle('QCheckBox')
        else:
            self.setWindowTitle('')

    def showDate(self, data):
        # 设置lebal标签的内容
        self.lb.setText('选择日期: ' + data.toString())

    def comboChange(self, item):
        self.lb.setText('当前选项: ' + str(item))


# QTimer组件
# 如果需要在程序中周期性地进行某项操作，比如检测某种设备的状态，就会用到定时器。
class time(QWidget):

    def init(self):
        # 创建QTimer对象
        self.qtime = QTimer(self)
        # 用于显示的label
        label = QLabel('计时器')
        self.label2 = QLabel()
        # 布局器
        grid = QGridLayout()
        grid.addWidget(label,1,1)
        grid.addWidget(self.label2,1,2)
        # 信号
        self.qtime.timeout.connect(self.showTime)
        # 启动QTimer, 参数为毫秒数.即多少时间执行一次
        self.qtime.start(1000)
        self.setLayout(grid)
        self.setGeometry(300,300,300,100)
        self.show()

    # 信号调用方法
    # 计时器
    count = 0
    def showTime(self):
        self.count += 1
        # 显示计时到label2
        self.label2.setText(str(self.count))
        # 停止计时
        if self.count == 1000:
            self.qtime.stop()






















if __name__ == '__main__':
    # 创建一个QApplication 对象，这是必须的一个步骤
    # 应用进入主循环。在这个地方，事件处理开始执行。主循环用于接收来自窗口触发的事件，并且转发他们到widget应用上处理。
    app = QApplication(sys.argv)
    # 创建Qwidget对象
    #qw = Gui()
    #mt = menu_tool()
    #lt = layout()
    #exam = singal()
    #dlog = dialog()
    #widget = widgets()
    qtime = time()

    # Qwidget对象方法调用
    #qw.init()
    #mt.init()
    #lt.init()
    #exam.init()
    #dlog.init()
    #widget.init()
    qtime.init()


    # 如果我们调用exit()方法或主widget组件被销毁，主循环将退出。sys.exit()方法确保一个不留垃圾的退出。系统环境将会被通知应用是怎样被结束的。
    # exec_()方法有一个下划线。因为exec是Python保留关键字。因此，用exec_()来代替。
    sys.exit(app.exec_())


